import os
import jinja2

import model
from utils.file_util import FileUtil

from .gen_context import GenContext
from .gen_module import GenModule
from .gen import Generator
from .factory_model import FactoryModel
from .factory_model_class import FactoryModelClass


class JsonSerializableGenerator:
    namespace_name = "uml"

    def __init__(self, output_path: str) -> None:
        self.output_path = output_path
        self.model: FactoryModel = FactoryModel()
        self.uml_model: model.UmlModel = model.UmlModel()

        self.env = jinja2.Environment(
            trim_blocks=True,
            lstrip_blocks=True,
            keep_trailing_newline=True,
            loader=jinja2.FileSystemLoader(FileUtil.get_path("gen/templates/cpp")),
        )

    def gen_model(self, mod: model.UmlModel) -> None:
        self.uml_model = mod
        self.__collect_model(mod)
        self.__gen_factory()

    def __gen_factory(self) -> None:
        params: dict = {
            "namespace_name": JsonSerializableGenerator.namespace_name,
            "model": self.model,
        }

        context: GenContext = GenContext(self.output_path)
        context.modules.append(
            GenModule(
                "jsonSerializable.cpp",
                "jsonSerializable.cpp.j2",
                "JsonSerializable.cpp",
            )
        )
        # context.modules.append(
        #     GenModule(
        #         "jsonSerializable.h",
        #         "jsonSerializable.h.j2",
        #         "JsonSerializable.h",
        #     ),
        # )

        for module in context.modules:
            template = self.env.get_template(module.template)
            FileUtil.write_text_file_if_change(
                os.path.join(FileUtil.get_path(context.root), module.output_path),
                template.render(params),
            )

    def __collect_model(self, model: model.UmlModel) -> None:
        self.model.classes.clear()
        for model_key, package in model.packageDict.items():
            self.__collect_package(package, model_key)

        for model_key, package in model.profileDict.items():
            self.__collect_package(package, model_key)
            
        self.model.classes = sorted(self.model.classes, key=lambda x: x.path)
        

    def __collect_package(
        self, package: model.UmlPackage | model.UmlProfile, model_key: str
    ) -> None:
        if package is None:
            return
        
        if model_key == "di":
            return
        
        for element in package.packagedElements.values():
            if element.xmi_type == model.UmlXmiInfo.type_package:
                self.__collect_package(element, model_key)
            elif element.xmi_type == model.UmlXmiInfo.type_class:
                self.__gen_class(element, model_key)
            elif element.xmi_type == model.UmlXmiInfo.type_data_type:
                self.__gen_class(element, model_key)
            elif element.xmi_type == model.UmlXmiInfo.type_enumeration:
                pass
            elif element.xmi_type == model.UmlXmiInfo.type_primitive_type:
                pass
            elif element.xmi_type == model.UmlXmiInfo.type_extension:
                pass
            elif element.xmi_type == model.UmlXmiInfo.type_association:
                pass
            elif element.xmi_type == model.UmlXmiInfo.type_package_import:
                pass
            elif element.xmi_type == model.UmlXmiInfo.type_stereotype:
                pass
            else:
                print(element.xmi_type, element.xmi_id)

    def __gen_class(self, cls: model.UmlClass, model_key: str) -> None:
        if cls is None:
            return
        
        if model_key == "di":
            return

        if self.__is_partial_class(cls):
            return
        
        # 抽象类
        for op in cls.ownedOperations:
            if op.isAbstract == "true":
                return

        model_cls: FactoryModelClass = FactoryModelClass()
        model_cls.name = cls.name
        model_cls.path = self.__get_type_path(cls.name, model_key)
        if len(cls.generalizations) == 0:
            if cls.name not in Generator.base_element_classes:
                model_cls.simpleClass = True
        self.model.classes.append(model_cls)

    def __get_type_path(self, type_name: str, model_key: str) -> str:
        return f"../{model_key.replace('_', '')}/{type_name}.h"
    
    def __is_partial_class(self, cls: model.UmlClass) -> bool:
        if cls.isPartialElement:
            return True
        
        if len(cls.generalizations) == 0:
            return False
        
        all_parent_partial: bool = True
        for gen in cls.generalizations:
            if gen.general == "BaseElement":
                all_parent_partial = False
                break
            if gen.general.endswith("#PackageableElement"):
                continue
            parent = self.uml_model.classes.get(gen.general)
            if not parent:
                print(gen.general)
                all_parent_partial = False
                break
            if not parent or not self.__is_partial_class(parent):
                all_parent_partial = False
                break
        
        return all_parent_partial

